package com.dhms.vostok1.ui.webview

import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.*
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.dhms.uikit.DialogBuilder
import com.dhms.uikit.media.MediaActivity
import com.dhms.uikit.media.MediaActivity.Companion.BROADCAST_FILE_PATH_KEY
import com.dhms.uikit.media.MediaChooseOptionsBottomSheet
import com.dhms.vostok1.model.SettingViewModel
import com.dhms.vostok1.model.UserViewModel
import com.dhms.vostok1.ui.main.MainActivityInterface
import com.dhms.webview.R
import com.dhms.webview.databinding.FragmentWebViewBinding
import java.util.*

class WebViewFragment : Fragment() {

    private var _binding: FragmentWebViewBinding? = null
    val binding get() = _binding!!
    private var isFragmentViewInit = false

    private val args: WebViewFragmentArgs by navArgs()

    private val userViewModel: UserViewModel by activityViewModels()
    private val settingViewModel: SettingViewModel by activityViewModels()

    private val onBackPressedCallback by lazy {
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (binding.webView.canGoBack()) {
                    binding.webView.goBack()
                } else {
                    findNavController().navigateUp()
                }
            }
        }
    }

    private val dataToPrepare = mutableMapOf<String, String?>()

    private var fileChooserCallback: ValueCallback<Array<Uri>>? = null

    private val fragmentId by lazy { UUID.randomUUID().toString() }

    private var isMediaBroadcastReceiverRegistered = false

    /*是否刷新webView*/
    var webViewRefresh = false

    private val mediaChooseOptionsBottomSheet by lazy {
        MediaChooseOptionsBottomSheet(fragmentId)
    }

    private lateinit var webVieUpgradingBroadcastReceiver: WebVieUpgradingBroadcastReceiver
    private val webVieUpgradingBroadcastIntentFilter: IntentFilter by lazy {
        IntentFilter().apply {
            addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
        }
    }

    private var isWebViewBroadcastReceiverRegistered = false

    private val serverAddress: String by lazy {
        settingViewModel.settingMap.value!!["BASE_URL"] as String
    }

    private val webViewNotSupportedDialog by lazy {
        DialogBuilder(requireContext()).setTitle(getString(R.string.tip_title))
            .setMessage(getString(R.string.update_tip_message))
            .setOnPositiveButtonClickListener {
                upgradeWebView()
                true
            }.setOnNegativeButtonClickListener {
                findNavController().navigateUp()
                true
            }.setIsCancelable(false).createDialog()
    }

    private var webViewIsSupported = false

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        if (_binding == null) {
            _binding = FragmentWebViewBinding.inflate(inflater, container, false)
        }
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (!isFragmentViewInit) {
            binding.lifecycleOwner = viewLifecycleOwner
            if (checkWebViewVersion()) {
                getDataToPrepare()

                initWebView()
            }
        }
        isFragmentViewInit = true
        if (webViewRefresh) {
            binding.webView.apply {
                reload()
            }
            webViewRefresh = false
        }
    }

    private fun checkWebViewVersion(): Boolean {
        /**
         * 1: 不支持
         * 2: 支持
         * 3: 需要更新
         */
        var webViewSupportState = 1

        try {
            val packageInfo =
                requireActivity().packageManager.getPackageInfo(WEBVIEW_PACKAGE_NAME, 0)
            Log.i(TAG, "WebView's version is ${packageInfo.versionName}")
            webViewSupportState =
                if (packageInfo.versionName.split(".").first().toInt() < MIN_WEWVIEW_VERSION) {
                    3
                } else {
                    2
                }
        } catch (error: PackageManager.NameNotFoundException) {
            Log.i(TAG, "WebView's package name($WEBVIEW_PACKAGE_NAME) not found")
            error.printStackTrace()
            webViewSupportState = 2
        } catch (error: Exception) {
            Log.i(TAG, "WebView version check failed")
            error.printStackTrace()
        }

        if (webViewSupportState == 1) {
            (requireActivity() as MainActivityInterface).showToast(
                message = "当前webView不支持",
                timeOut = 2
            )
            findNavController().navigateUp()
            webViewIsSupported = false
            return false
        }
        if (webViewSupportState == 2) {
            webViewIsSupported = true
            return true
        }
        if (webViewSupportState == 3) {
            webViewNotSupportedDialog.show()
            return false
        }

        return false
    }

    private fun upgradeWebView() {
        try {
            val apiHost = settingViewModel.settingMap.value!!["BASE_URL"] as String
            val downloadReq =
                DownloadManager.Request(Uri.parse(apiHost + WEBVIEW_UPDATING_FILE_URL))
            downloadReq.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
            downloadReq.setDestinationInExternalFilesDir(
                requireContext(),
                Environment.DIRECTORY_DOWNLOADS, WEBVIEW_UPDATING_FILE_NAME
            )
            downloadReq.setTitle(WEBVIEW_UPDATING_FILE_NAME)
            downloadReq.setMimeType("application/vnd.android.package-archive")
            val downloadManager =
                requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
            webVieUpgradingBroadcastReceiver = WebVieUpgradingBroadcastReceiver()
            requireActivity().registerReceiver(
                webVieUpgradingBroadcastReceiver,
                webVieUpgradingBroadcastIntentFilter
            )
            (requireActivity() as MainActivityInterface).showToast(
                message = "下载中",
                isLoading = true,
                timeOut = 0
            )
            downloadManager.enqueue(downloadReq)
            isWebViewBroadcastReceiverRegistered = true
        } catch (e: Exception) {
            e.printStackTrace()
            (requireActivity() as MainActivityInterface).showToast(
                R.drawable.ic_fail,
                message = "下载失败",
                timeOut = 2
            )
        }
    }

    private fun getDataToPrepare() {
        dataToPrepare["AUTH_TOKEN"] = userViewModel.userInfo.value?.token
        dataToPrepare["USER_INFO"] = userViewModel.userInfo.value?.originLoginData
        dataToPrepare["ORGANIZATION"] = userViewModel.userInfo.value?.originAclData
        dataToPrepare["SELECTED_THEME-1"] =
            if (requireActivity().applicationContext.resources.configuration.uiMode == 0x21) "dark" else "light"
        dataToPrepare["BUY_SERVICE_LIST"] = "[\"user\"]"
        dataToPrepare["SELECTED_SERVICE"] = "user"
        dataToPrepare["TRANSFER_MODE"] = "app"
    }

    private fun initWebView() {
        var isDataInitialized = false
        var initialDataJs = ""
        dataToPrepare.forEach {
            initialDataJs += "window.localStorage.setItem('${it.key}', '${formatJs(it.value.toString())}');"
        }

        val baseUrl = if (webViewIsSupported) {
            if (args.url.startsWith("http")) {
                args.url
            } else {
                serverAddress + args.url
            }
        } else "about:blank"

        WebView.setWebContentsDebuggingEnabled(true)

        binding.webView.apply {
            this.settings.cacheMode = WebSettings.LOAD_DEFAULT
            this.settings.domStorageEnabled = true

            webChromeClient = object : WebChromeClient() {

                override fun onProgressChanged(view: WebView?, newProgress: Int) {
                    binding.progress = newProgress
                    super.onProgressChanged(view, newProgress)
                }

                override fun onShowFileChooser(
                    webView: WebView?,
                    filePathCallback: ValueCallback<Array<Uri>>?,
                    fileChooserParams: FileChooserParams?,
                ): Boolean {
                    fileChooserCallback = filePathCallback
                    openFileChooser(fileChooserParams)

                    return true
                }
            }

            webViewClient = object : WebViewClient() {
//                override fun shouldOverrideUrlLoading(
//                    view: WebView?,
//                    request: WebResourceRequest?
//                ): Boolean {
//                    val url = request?.url.toString()
//                    if (url == baseUrl) {
//                        return false
//                    }
//                    if (false) {
//                        // TODO("intercept the url scheme")
//                    } else {
//                        view?.loadUrl(url)
//                    }
//
//                    return super.shouldOverrideUrlLoading(view, request)
//                }

                override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                    if (!isDataInitialized) {
                        evaluateJavascript(initialDataJs) {
                            Log.d(TAG, "The result to Init data to LocalStorage is $it.")
                        }
                        isDataInitialized = true
                    }
                    super.onPageStarted(view, url, favicon)
                }

                override fun onReceivedError(
                    view: WebView?,
                    request: WebResourceRequest?,
                    error: WebResourceError?
                ) {
                    val errorMessage = when (error?.errorCode) {
                        ERROR_CONNECT -> "连接失败，请检查网络后重试"
                        ERROR_BAD_URL -> "服务器地址异常，请重试"
                        ERROR_HOST_LOOKUP -> "网络错误，请检查网络后重试"
                        ERROR_FAILED_SSL_HANDSHAKE -> "网络错误，请检查网络后重试"
                        ERROR_PROXY_AUTHENTICATION -> "代理错误，请检查网络代理后重试"
                        ERROR_TIMEOUT -> "请求超时，请检查网络后重试"
                        ERROR_AUTHENTICATION -> "授权失败，请重试"
                        else -> "未知错误"
                    }
                    (requireActivity() as MainActivityInterface).showToast(
                        R.drawable.ic_fail,
                        errorMessage,
                        timeOut = 2
                    )
                    Log.d(TAG, "A error occurred: ${error?.errorCode}.")
                    super.onReceivedError(view, request, error)
                }
            }
            settings.javaScriptEnabled = true
            settings.domStorageEnabled = true
            settings.allowFileAccess = true
            settings.defaultTextEncodingName = "utf-8"

            addJavascriptInterface(WebViewInterface(binding.webView), "NativeMethods")

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                loadUrl(baseUrl)
            } else {
                val injection =
                    "<script type='text/javascript'>$initialDataJs;window.location.replace('$baseUrl');</script>";
                loadDataWithBaseURL(
                    baseUrl,
                    injection,
                    "text/html",
                    "utf-8",
                    null
                )
            }
        }
    }

    private fun openFileChooser(
        fileChooserParams: WebChromeClient.FileChooserParams?
    ) {
        val type = fileChooserParams?.acceptTypes?.get(0).let {
            if (it.isNullOrEmpty()) {
                "*/*"
            } else it
        }

        registerMediaBroadcastReceiver()
        mediaChooseOptionsBottomSheet.setOnMediaChosenCallback(fileChooserCallback)
            .setMediaType(type).show(childFragmentManager, TAG)
    }

    private val onMediaCapturedBroadcastReceiver by lazy {
        object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                val fileUri = intent?.extras?.getParcelable<Uri>(BROADCAST_FILE_PATH_KEY)
                if (fileUri != null) {
                    fileChooserCallback?.onReceiveValue(arrayOf(fileUri))
                } else {
                    fileChooserCallback?.onReceiveValue(arrayOf())
                }
                fileChooserCallback = null
            }
        }
    }

    private fun registerMediaBroadcastReceiver() {
        if (!isMediaBroadcastReceiverRegistered) {
            val intentFilter = IntentFilter(MediaActivity.BROADCAST_CREATE_ACTION + fragmentId)
            requireActivity().registerReceiver(onMediaCapturedBroadcastReceiver, intentFilter)
            isMediaBroadcastReceiverRegistered = true
        }
    }

    override fun onResume() {
        super.onResume()
        requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
    }

    override fun onStop() {
        onBackPressedCallback.remove()
        super.onPause()
    }

    override fun onDetach() {
        if (_binding != null) {
            binding.webView.destroy()
            _binding = null
            isFragmentViewInit = false
        }
        if (isMediaBroadcastReceiverRegistered) {
            requireActivity().unregisterReceiver(onMediaCapturedBroadcastReceiver)
        }
        if (isWebViewBroadcastReceiverRegistered) {
            requireActivity().unregisterReceiver(webVieUpgradingBroadcastReceiver)
        }
        super.onDetach()
    }

    inner class WebVieUpgradingBroadcastReceiver : BroadcastReceiver() {
        private val downloadCompleteDialog by lazy {
            DialogBuilder(requireContext()).setTitle(getString(R.string.download_complete_title))
                .setMessage(getString(R.string.install_tip_message))
                .setOnPositiveButtonClickListener {
                    (requireActivity() as MainActivityInterface).hideToast()
                    // 展开通知栏
                    val service = requireContext().getSystemService("statusbar")
                    val statusBarManager = Class.forName("android.app.StatusBarManager")
                    val collapse =
                        statusBarManager.getMethod("expandNotificationsPanel")
                    collapse.isAccessible = true
                    collapse.invoke(service)
                    Toast.makeText(
                        requireContext(),
                        getString(R.string.finish_tip_message),
                        Toast.LENGTH_LONG
                    ).show()
                    requireActivity().finish()
                    true
                }.setIsCancelable(false).createDialog()
        }

        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
                downloadCompleteDialog.show()
            }
        }
    }

    companion object {
        private const val TAG = "WebViewFragment"

        private const val WEBVIEW_PACKAGE_NAME = "com.google.android.webview"
        private const val MIN_WEWVIEW_VERSION = 67

        private const val WEBVIEW_UPDATING_FILE_NAME = "WebView.apk"

        private const val WEBVIEW_UPDATING_FILE_URL =
            "/app_product/local_download/WebView_67.0.3396.87.apk"

        // 将部分字符转义
        fun formatJs(originJs: String): String {
            return originJs.replace(Regex("['(){}]")) {
                "\\" + it.value
            }
        }
    }
}